1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 /** 12 * This file provides the essential information for specifying vertices 13 * for the target 3D API. Its Attributes/Layout, some preset layouts. 14 * The workflow for vertices are entirely based on OpenGL, using VAOs and VBOs 15 * 16 */ 17 18 module hip.hiprenderer.vertex; 19 import hip.hiprenderer.renderer; 20 import hip.error.handler; 21 import hip.console.log; 22 public import hip.api.renderer.vertex; 23 24 25 26 27 private __gshared HipVertexArrayObject lastBoundVertex; 28 29 /** 30 * For using this class, you must first define the vertex layout for after that, create the vertex 31 * buffer and/or the index buffer. 32 */ 33 class HipVertexArrayObject 34 { 35 IHipVertexArrayImpl VAO; 36 IHipRendererBuffer VBO; 37 IHipRendererBuffer EBO; 38 ///Accumulated size of the vertex data 39 uint stride; 40 ///How many data slots it uses, for instance, vec3 will count +3 41 uint dataCount; 42 HipVertexAttributeInfo[] infos; 43 44 bool isBonded; 45 protected bool hasVertexInitialized; 46 protected bool hasIndexInitialized; 47 48 /** 49 * Remember calling sendAttributes 50 */ 51 this() 52 { 53 isBonded = false; 54 this.VAO = HipRenderer.createVertexArray(); 55 } 56 57 /** 58 * Creates and binds an index buffer. 59 */ 60 void createIndexBuffer(index_t count, HipResourceUsage usage) 61 { 62 assert(EBO is null, "Can't create buffer if it is already assigned."); 63 this.EBO = HipRenderer.createBuffer(count*index_t.sizeof, usage, HipRendererBufferType.index); 64 } 65 /** 66 * Sets the index buffer. Mainly useful for sharing multiple index buffer (quads and etc) 67 */ 68 void setIndexBuffer(IHipRendererBuffer buffer) 69 { 70 this.EBO = buffer; 71 } 72 73 /** 74 * Creates and binds a vertex buffer. 75 * 76 * The vertex buffer size is dependant on the attributes that were appended to this vertex array. 77 */ 78 void createVertexBuffer(uint count, HipResourceUsage usage) 79 { 80 this.VBO = HipRenderer.createBuffer(count*this.stride, usage, HipRendererBufferType.vertex); 81 } 82 /** 83 * This function creates an attribute information, 84 * for later sending it(it is necessary as the stride needs to be recalculated) 85 */ 86 HipVertexArrayObject appendAttribute( 87 uint count, 88 HipAttributeType valueType, 89 uint typeSize, 90 string infoName, 91 bool isPadding = false, 92 ) 93 { 94 HipVertexAttributeInfo info = HipVertexAttributeInfo( 95 name: infoName, 96 count: count, 97 valueType: valueType, 98 typeSize: typeSize, 99 index: cast(uint)infos.length, 100 //It actually is the `last stride`, which is the same as the offset is the total current stride 101 offset: stride 102 ); 103 104 info.offset = stride; 105 // if(!isPadding) 106 { 107 infos~= info; 108 dataCount+= count; 109 } 110 stride+= count*typeSize; 111 return this; 112 } 113 114 HipVertexArrayObject appendAttribute(T)(string infoName, bool isPadding = false) 115 { 116 uint count = 1; 117 HipAttributeType type = HipAttributeType.Float; 118 uint typeSize = float.sizeof; 119 import hip.math.vector; 120 121 static if(is(T == Vector2)) count = 2; 122 else static if(is(T == Vector3)) count = 3; 123 else static if(is(T == Vector4) || is(T == HipColorf)) count = 4; 124 else static if(is(T == HipColor)) 125 { 126 type = HipAttributeType.Rgba32; 127 count = 4; 128 typeSize = ubyte.sizeof; 129 } 130 else 131 { 132 static if(is(T == int)) type = HipAttributeType.Int; 133 else static if(is(T == uint)) type = HipAttributeType.Uint; 134 else static if(is(T == bool)) type = HipAttributeType.Bool; 135 else 136 static assert(is(T == float), "Unrecognized type for attribute: "~T.stringof); 137 138 typeSize = T.sizeof; 139 } 140 return appendAttribute(count, type, typeSize ,infoName, isPadding); 141 } 142 143 /** 144 * Sets the attribute infos that were appended to this object. This function must only be called 145 * after binding/creating a VBO, or it will fail 146 */ 147 void sendAttributes(Shader s) 148 { 149 // if(!isBonded) 150 // { 151 // ErrorHandler.showErrorMessage("VertexArrayObject error", "VAO wasn't bound when trying to send its attributes"); 152 // return; 153 // } 154 this.VAO.createInputLayout(VBO, EBO, infos, stride, s.vertexShader, s.shaderProgram); 155 } 156 157 void bind() 158 { 159 static if(UseDelayedUnbinding) 160 { 161 if(lastBoundVertex is this) 162 return; 163 if(lastBoundVertex !is null) 164 { 165 lastBoundVertex.isBonded = false; 166 lastBoundVertex.VAO.unbind(lastBoundVertex.VBO, lastBoundVertex.EBO); 167 } 168 lastBoundVertex = this; 169 } 170 if(!this.isBonded) 171 { 172 isBonded = true; 173 this.VAO.bind(this.VBO, this.EBO); 174 } 175 else assert(false, "Erroneous bind."); 176 } 177 void unbind() 178 { 179 static if(UseDelayedUnbinding) 180 return; 181 if(this.isBonded) 182 { 183 isBonded = false; 184 this.VAO.unbind(this.VBO, this.EBO); 185 } 186 else assert(false, "Erroneous unbind."); 187 } 188 189 /** 190 * Sets the VBO data. Use this function only for initialization as it allocates memory. 191 * 192 * If you wish to only update its data, call updateVertices instead. 193 */ 194 void setVertices(const void[] data) 195 { 196 if(VBO is null) 197 ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices"); 198 else 199 { 200 hasVertexInitialized = true; 201 this.VBO.setData(data); 202 } 203 } 204 /** 205 * Update the VBO. Won't cause memory allocation. 206 * Params: 207 * count = How many vertices to update 208 * data = The data containing a type which is conforming to the VAO. 209 * offset = The offset is always multiplied by this vertex array object stride. 210 */ 211 void updateVertices(const void[] data, int offset = 0) 212 { 213 if(VBO is null) 214 ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices"); 215 ErrorHandler.assertExit(hasVertexInitialized, "Vertex must setData before updating its contents."); 216 this.VBO.updateData(offset*this.stride, data); 217 } 218 /** 219 * Will set the indices data. Beware that this function may allocate memory. 220 * 221 * If you need to only change its data value instead of allocating memory for a greater index buffer 222 * call updateIndices 223 */ 224 void setIndices(const index_t[] data) 225 { 226 if(EBO is null) 227 ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices"); 228 else 229 { 230 hasIndexInitialized = true; 231 this.EBO.setData(data); 232 } 233 } 234 /** 235 * Updates the index buffer's data. It won't allocate memory 236 */ 237 void updateIndices(const index_t[] data, int offset = 0) 238 { 239 if(EBO is null) 240 ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices"); 241 else 242 { 243 ErrorHandler.assertExit(hasIndexInitialized, "Index must setData before updating its contents."); 244 this.EBO.updateData(cast(int)(offset*index_t.sizeof), data); 245 } 246 } 247 248 /** 249 * Receives a struct and creates a VAO based on its member types and names. 250 */ 251 static HipVertexArrayObject getVAO(T)() if(is(T == struct)) 252 { 253 import std.traits:isFunction; 254 import hip.util.reflection:hasUDA; 255 256 HipVertexArrayObject obj = new HipVertexArrayObject(); 257 static foreach(member; __traits(allMembers, T)) 258 {{ 259 alias mem = __traits(getMember, T, member); 260 static if(!isFunction!(mem) && __traits(compiles, mem.offsetof)) 261 { 262 obj.appendAttribute!((typeof(mem))) 263 ( 264 member, 265 hasUDA!(mem, HipShaderInputPadding) 266 ); 267 } 268 }} 269 return obj; 270 } 271 272 }